﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NeoPatterns.Patterns.Adapter;
using NeoPatterns.Patterns.UniformAccessor;
using NeoPatterns.Utility;

namespace NeoPatterns.Patterns.UniformMutator
{
    public class ReflectionUniformMutator : ReflectionUniformAccessor, IUniformMutator
    {
        /// <summary>
        /// A mutator knows how to mutate a class, given a collection of parameters
        /// </summary>
        protected class Mutator
        {
            /// <summary>
            /// Class type to mutate
            /// </summary>
            public Type ClassType { get; private set; }
            /// <summary>
            /// The mutator works if the following members are provided
            /// </summary>
            public IList<string> ExpectedMembers { get; private set; }
            /// <summary>
            /// Method to be called to mutate
            /// implicit parameters
            /// </summary>
            private readonly Delegate mutate;

            /// <summary>
            /// Wrap call to mutator (to ensure parameters safety)
            /// </summary>
            /// <param name="holder"></param>
            /// <param name="members"></param>
            /// <returns></returns>
            public object Mutate(object holder, IDictionary<MemberRef, object> members)
            {
                return mutate.DynamicInvoke(holder, members);
            }

            private Mutator(Type classType, IList<string> expectedMembers, Delegate mutate)
            {
                ClassType = classType;
                ExpectedMembers = expectedMembers;
                this.mutate = mutate;
            }

            /// <summary>
            /// Registers a mutator for given type and expected members
            /// </summary>
            /// <typeparam name="TObject"></typeparam>
            /// <param name="classType"></param>
            /// <param name="expectedMembers"></param>
            /// <param name="mutateDelegate"></param>
            /// <returns></returns>
            public static Mutator Create<TObject>(Type classType, IList<string> expectedMembers,
                Func<TObject, IDictionary<MemberRef, object>, TObject> mutateDelegate)
            {
                return new Mutator(classType, expectedMembers, mutateDelegate);
            }
        }

        /// <summary>
        /// Creates a mutable version of a given instance of a given type
        /// </summary>
        protected class MutableCreator
        {
            /// <summary>
            /// The immutable version of the type
            /// </summary>
            public Type ImmutableType { get; private set; }

            /// <summary>
            /// The target type of the mutable implementation
            /// </summary>
            public Type MutableTypeImplementation { get; private set; }

            /// <summary>
            /// The delegate that creates the mutable version
            /// (free advertising: use the ReflectionPrototyper)
            /// </summary>
            private readonly Delegate cloneAsMutable;

            /// <summary>
            /// Creates a mutable clone of a given and supposed immutable source
            /// </summary>
            /// <param name="immutableSource"></param>
            /// <returns></returns>
            public object CloneAsMutable(object immutableSource)
            {
                return cloneAsMutable.DynamicInvoke(immutableSource);
            }

            private MutableCreator(Type immutableType, Type mutableType, Delegate cloneAsMutableDelegate)
            {
                ImmutableType = immutableType;
                MutableTypeImplementation = mutableType;
                cloneAsMutable = cloneAsMutableDelegate;
            }

            /// <summary>
            /// Creates an instance
            /// </summary>
            /// <typeparam name="TImmutable"></typeparam>
            /// <typeparam name="TMutable"></typeparam>
            /// <param name="mutableType"></param>
            /// <param name="cloneAsMutableDelegate"></param>
            /// <param name="immutableType"></param>
            /// <returns></returns>
            public static MutableCreator Create<TImmutable, TMutable>(Type immutableType, Type mutableType, Func<TImmutable, TMutable> cloneAsMutableDelegate)
            {
                // we do this because we can keep a type checking for the cloneAsMutableDelegate
                return new MutableCreator(immutableType, mutableType, cloneAsMutableDelegate);
            }
        }

        /// <summary>
        /// A custom mutator is defined by:
        /// - Its type
        /// - The members to be changed
        /// On the contrary of UniformAccessor which handles only members of same type at once, 
        /// the mutator allows to access several member types
        /// </summary>
        private readonly IDictionary<Type, IList<Mutator>> mutatorsByType = new Dictionary<Type, IList<Mutator>>();

        /// <summary>
        /// This dictionary contains methods to ensure that a given type instance is mutable, or return a new one if required
        /// </summary>
        private readonly IDictionary<Type, MutableCreator> mutableCreatorsByType = new Dictionary<Type, MutableCreator>();

        protected Mutator FindMutator(Type classType, IEnumerable<MemberRef> memberNames, out IList<string> missingMembers)
        {
            IList<Mutator> mutators;
            Mutator bestMutator = null;
            missingMembers = new List<string>();

            lock (mutatorsByType)
            {
                if (!mutatorsByType.TryGetValue(classType, out mutators))
                    return null;
            }

            int bestMutatorNote = int.MaxValue;
            foreach (var mutator in mutators)
            {
                // for each mutator, we will give an evaluation (0 is best, >0 is worse)
                // if we have a member requested not supported by the mutator, then we eliminate this mutator
                // if we have a member in the mutator that is not present in the given parameter, we add 1 to the evaluation

                // 1. check if the mutator can handle
                bool canHandle = true;
                foreach (var memberName in memberNames)
                {
                    var localMemberName = memberName;
                    if (!mutator.ExpectedMembers.Any(m => m == localMemberName.Name))
                    {
                        canHandle = false;
                        break;
                    }
                }

                // this is the case where we have arguments that mutator doesn't handle
                if (!canHandle)
                    continue;

                // 2. check all members
                int note = 0;
                foreach (var expectedMember in mutator.ExpectedMembers)
                {
                    var localExpectedMember = expectedMember;
                    // each time the mutator requires a parameter we don't have,
                    // we add one point
                    if (!memberNames.Any(m => m.Name == localExpectedMember))
                    {
                        missingMembers.Add(localExpectedMember);
                        ++note;
                    }
                }

                // if we have a better evaluation, the mutator is more appropriate
                if (bestMutator == null || note < bestMutatorNote)
                {
                    bestMutator = mutator;
                    bestMutatorNote = note;
                }

                // we just found the perfect mutator. How lucky we are
                if (bestMutatorNote == 0)
                    break;
            }
            return bestMutator;
        }

        /// <summary>
        /// Registers a mutator for a given type
        /// </summary>
        /// <param name="mutator"></param>
        protected void RegisterMutator(Mutator mutator)
        {
            lock (mutatorsByType)
            {
                IList<Mutator> mutators;
                // Find list
                if (!mutatorsByType.TryGetValue(mutator.ClassType, out mutators))
                {
                    mutators = new List<Mutator>();
                    mutatorsByType[mutator.ClassType] = mutators;
                }

                // then register the new mutator
                mutators.Add(mutator);
            }
        }

        /// <summary>
        /// Registers a mutator, with explicit class type declaration
        /// </summary>
        /// <param name="classType"></param>
        /// <param name="handledMembers"></param>
        /// <param name="mutateFunc"></param>
        public void RegisterMutator(Type classType, IList<string> handledMembers, Func<object, IDictionary<MemberRef, object>, object> mutateFunc)
        {
            RegisterMutator(Mutator.Create(classType, handledMembers, mutateFunc));
        }

        /// <summary>
        /// Registers a mutator, with generic type declaration
        /// </summary>
        /// <typeparam name="TObject"></typeparam>
        /// <param name="handledMembers"></param>
        /// <param name="mutateFunc"></param>
        public void RegisterMutator<TObject>(IList<string> handledMembers, Func<TObject, IDictionary<MemberRef, object>, TObject> mutateFunc)
        {
            RegisterMutator(Mutator.Create(typeof(TObject), handledMembers, mutateFunc));
        }

        protected void RegisterCloneAsMutable(MutableCreator mutableCreator)
        {
            lock (mutableCreatorsByType)
            {
                mutableCreatorsByType[mutableCreator.ImmutableType] = mutableCreator;
            }
        }

        /// <summary>
        /// Registers a "clone as mutable"
        /// </summary>
        /// <param name="mutableType"></param>
        /// <param name="cloneAsMutable"></param>
        /// <param name="immutableType"></param>
        public void RegisterCloneAsMutable(Type immutableType, Type mutableType, Func<object, object> cloneAsMutable)
        {
            RegisterCloneAsMutable(MutableCreator.Create(immutableType, mutableType, cloneAsMutable));
        }

        /// <summary>
        /// Registers a cloneAsMutable, generic version
        /// </summary>
        /// <typeparam name="TImmutable"></typeparam>
        /// <typeparam name="TMutable"></typeparam>
        /// <param name="cloneAsMutable"></param>
        public void RegisterCloneAsMutable<TImmutable, TMutable>(Func<TImmutable, TMutable> cloneAsMutable)
        {
            RegisterCloneAsMutable(MutableCreator.Create(typeof(TImmutable), typeof(TMutable), cloneAsMutable));
        }

        /// <summary>
        /// Sets a member value given a MemberRef
        /// </summary>
        /// <param name="holder"></param>
        /// <param name="memberRef"></param>
        /// <param name="value"></param>
        /// <param name="arrayAdapters"></param>
        protected virtual void SetMember(object holder, MemberRef memberRef, object value, IDictionary<string, IArrayAdapter> arrayAdapters)
        {
            var memberInfo = GetMemberInfo(holder, memberRef);
            // if we have an array
            if (memberRef.Index.HasValue)
            {
                // the adapter allows to enumerate through some diverse forms of lists
                IArrayAdapter arrayAdapter;
                // a cache can be passed as parameter
                if (arrayAdapters == null || !arrayAdapters.TryGetValue(memberRef.Name, out arrayAdapter))
                {
                    arrayAdapter = GetMemberArrayAdapter(holder, memberRef.Name);
                    if (arrayAdapters != null)
                        arrayAdapters[memberRef.Name] = arrayAdapter;
                }
                arrayAdapter[memberRef.Index.Value] = value;
            }
            else
            {
                // direct object
                var propertyInfo = memberInfo as PropertyInfo;
                if (propertyInfo != null)
                {
                    var setMethod = propertyInfo.GetSetMethod();
                    if (setMethod != null)
                        setMethod.Invoke(holder, new[] { value });
                }
                else
                    memberInfo.SetMemberValue(holder, value);
            }
        }

        /// <summary>
        /// Mutate object by injection:
        /// fields and properties are written directly
        /// </summary>
        /// <param name="holder"></param>
        /// <param name="memberValues"></param>
        protected virtual void MutateByInjection(object holder, IDictionary<MemberRef, object> memberValues)
        {
            var arrayAdapters = new Dictionary<string, IArrayAdapter>();
            // pretty simple here, each property/field is set individually
            foreach (var memberValue in memberValues)
            {
                SetMember(holder, memberValue.Key, memberValue.Value, arrayAdapters);
            }
        }

        /// <summary>
        /// Checks an object is mutable
        /// </summary>
        /// <param name="holder"></param>
        /// <param name="holderType"></param>
        /// <returns></returns>
        protected virtual object CheckMutable(object holder, Type holderType)
        {
            MutableCreator mutableCreator;
            if (!mutableCreatorsByType.TryGetValue(holderType, out mutableCreator))
                return holder;
            return mutableCreator.CloneAsMutable(holder);
        }

        /// <summary>
        /// Mutates object, with given new values.
        /// </summary>
        /// <typeparam name="TObject"></typeparam>
        /// <param name="holder"></param>
        /// <param name="memberValues"></param>
        /// <returns></returns>
        public TObject Mutate<TObject>(TObject holder, IDictionary<MemberRef, object> memberValues)
        {
            // we have three possible options
            // 1 (best). A mutator matches the given values
            // 2 (medium). A mutator has a superset of the given values, so we read the missing ones and reinject them
            // 3 (worst... or not?). No mutator matches (or none defined), we mutate the object by hand, hoping that all properties have setters
            IList<string> missingMembers;
            var mutator = FindMutator(holder.GetType(), memberValues.Keys, out missingMembers);
            if (mutator != null)
            {
                // no missing member is the optimal case, we have the mutator that handles all our values
                if (missingMembers.Count == 0)
                {
                    return (TObject)mutator.Mutate(holder, memberValues);
                }
                // non optimal case: get the missing parts
                var allMemberValues = new Dictionary<MemberRef, object>(memberValues);
                foreach (var missingMember in missingMembers)
                {
                    var memberName = new MemberRef(missingMember);
                    var memberInfo = GetMemberInfo(holder, memberName);
                    allMemberValues[memberName] = memberInfo.GetMemberValue(holder);
                }
                return (TObject)mutator.Mutate(holder, allMemberValues);
            }
            // poor man's mutator: checks the objects is mutable, then mutates it
            var mutableHolder = (TObject)CheckMutable(holder, typeof(TObject));
            MutateByInjection(mutableHolder, memberValues);
            return mutableHolder;
        }
    }
}
